//Test for Ventus NLE
//A sample implementation for a ffmpeg resource and class

#include "vus_test.h"
#include "vus_test_frame.h"
#include "vus_test_video_ffmpeg.h"

#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>

static int vus_init_avcodec = 0;

struct _vus_test_resource_video_ffmpeg {
	int file_opened;
	char *filename;
	AVFormatContext *format_ctx;
	int stream_id;
	AVCodecContext *codec_ctx;
	AVCodec *codec;
	
	int width, height;
	
	AVPacket packet;
	
	AVFrame *frame_src, *frame_dst;
	enum PixelFormat pix_fmt_src, pix_fmt_dst;
	
	int buffer_size;
	uint8_t *buffer;
	
	struct SwsContext *swsContext;
};

vus_test_resource_video_ffmpeg *vus_test_resource_video_ffmpeg_alloc() {
	vus_test_resource_video_ffmpeg *res;
	
	if (!vus_init_avcodec) {
		av_register_all();
	}
	
	res = (vus_test_resource_video_ffmpeg*)malloc(sizeof(vus_test_resource_video_ffmpeg));
	res->format_ctx = avformat_alloc_context();
	res->file_opened = 0;
	
	res->codec_ctx = 0;
	res->codec = 0;
	res->frame_src = res->frame_dst = 0;
	res->pix_fmt_src = res->pix_fmt_dst = 0;
	
	
	return res;
	//everything else will be alloc'd when a video file and codec is actually opened.
}

vus_frame *vus_test_resource_video_ffmpeg_get_next_frame(vus_test_resource_video_ffmpeg *res) {
	int frameRead = 0;
	
	while (1) {
		if (av_read_frame(res->format_ctx, &res->packet) != 0) {
			if (res->packet.data != NULL)
				av_free_packet(&res->packet);
			return NULL;
			
		}
		
		if (res->packet.stream_index == res->stream_id) {
			avcodec_decode_video2(res->codec_ctx, res->frame_src, &frameRead, &res->packet);
			if (frameRead) {
				
				sws_scale(res->swsContext, (const uint8_t * const *)res->frame_src->data, res->frame_src->linesize, 0, res->height, res->frame_dst->data, res->frame_dst->linesize);
				av_free_packet(&res->packet);
				return vus_frame_copy(res->width, res->height, res->frame_dst->data[0], res->frame_dst->linesize[0]);
			}
			
		}
		av_free_packet(&res->packet);
	}
}


int vus_get_first_video_stream_id(AVFormatContext *c) {
	unsigned int i;
	//identify a video stream
	for (i=0; i < c->nb_streams; i++) {
		if (c->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
			return i;
			break;
		}
	}
	//no video stream found
	fprintf(stderr, "%s: No video stream found\n", c->filename);
	return -1;
}


vus_result vus_test_resource_video_ffmpeg_open(vus_test_resource_video_ffmpeg *res, const char *filename) {
	assert(res != NULL);
	
	if (avformat_open_input(&res->format_ctx, filename, NULL, NULL) != 0) {
		return VUS_CANNOT_OPEN_FILE;
	}
	
	res->file_opened = 1;
	
	if (avformat_find_stream_info(res->format_ctx, 0) < 0) {
		return VUS_INVALID_FILE;
	}
	
	res->stream_id = vus_get_first_video_stream_id(res->format_ctx);
	if (res->stream_id < 0) {
		return VUS_INVALID_FILE;
	}
	
	res->codec_ctx = res->format_ctx->streams[res->stream_id]->codec;
	
	res->codec = avcodec_find_decoder(res->codec_ctx->codec_id);
	if (res->codec == NULL) {
		return VUS_UNSUPPORTED_CODEC;
	}
	
	if (avcodec_open2(res->codec_ctx, res->codec, NULL) < 0) {
		return VUS_CODEC_FAIL;
	} else {
		printf("Opened stream with %s\n", res->codec->name);
	}
	
	res->width = res->codec_ctx->width;
	res->height = res->codec_ctx->height;
	
	res->frame_src = avcodec_alloc_frame();
	res->frame_dst = avcodec_alloc_frame();
	if (res->frame_src == NULL || res->frame_dst == NULL)
		return VUS_OUT_OF_MEMORY;
		
	res->pix_fmt_src = res->codec_ctx->pix_fmt;
	res->pix_fmt_dst = PIX_FMT_RGB24;  //TODO: adapt to correct pix formats
	
	res->buffer_size = avpicture_get_size(res->pix_fmt_dst, res->width, res->height);
	res->buffer = (uint8_t*)av_malloc(res->buffer_size * sizeof(uint8_t));
	if (res->buffer == NULL)
		return VUS_OUT_OF_MEMORY;
	
	avpicture_fill((AVPicture*)res->frame_dst, res->buffer, res->pix_fmt_dst, res->width, res->height);
	
	res->swsContext = sws_getContext(res->width, res->height, res->pix_fmt_src, res->width, res->height, res->pix_fmt_dst, SWS_BICUBIC, NULL, NULL, NULL);
	
	return VUS_SUCCESS;
}

void vus_test_resource_video_ffmpeg_free(vus_test_resource_video_ffmpeg **res) {
	vus_test_resource_video_ffmpeg *r = *res;
	
	assert(r != NULL);
	
	if (r->file_opened) {
		sws_freeContext(r->swsContext);
		av_free(r->frame_src);
		av_free(r->frame_dst);
		av_free(r->buffer);
		avcodec_close(r->codec_ctx);
		avformat_close_input(&r->format_ctx);
	} else {
		avformat_free_context(r->format_ctx);
	}
	
	free(r);
	
	*res = 0;
}
